#pragma once

#include "util.hpp"
#include <cstring>
#include <fstream>
#include <iostream>
#include <unordered_map>
#include <vector>

namespace ns_index {
struct DocInfo {
  std::string title;
  std::string content;
  std::string url;
  uint64_t doc_id; //足够位
};
struct InvertedElem {
  uint64_t doc_id;
  std::string word; //怎样构造
  int weight;
  InvertedElem():weight(0){}
};

typedef std::vector<InvertedElem> InvertedList;


class Index {
private:
  std::vector<DocInfo> forward_index;
  std::unordered_map<std::string, InvertedList> inverted_index;//倒排索引的值描述为结构体
  Index() {}
  Index(const Index &) = delete;
  Index& operator=(const Index &) = delete;

  static Index* instance;

public:
  ~Index() {}

public:
  static Index* GetInstance()
  {
      static std::mutex mtx;
      if(instance==nullptr)
      {
        mtx.lock();
        if(instance==nullptr)
        {
          instance = new Index();
        }
        mtx.unlock();  
      }
      return instance;
  }
  DocInfo *GetForwardIndex(uint64_t doc_id)
  {
    if (doc_id >= forward_index.size())
    {
      std::cerr << "get forwardindex failed" << std::endl;
      return nullptr;
    }
    return &forward_index[doc_id];
  }
  InvertedList *GetInvertedList(const std::string &word) 
  {
      auto iter = inverted_index.find(word);
      if (iter == inverted_index.end())
      {
        std::cerr << word << " have no InvertedList" << std::endl;
        return nullptr;
      }
      return &(iter->second);
  }
  // parser处理后的数据不是string吗,怎么open,这个到底是什么--savehtml--序列化后的数据存储路径
  bool BuildIndex(const std::string &input) 
  {
      std::ifstream in(input, std::ios::in | std::ios::binary); //这个二进制的意义是什么
      if (!in.is_open()) 
      {
        std::cerr << "open file " << input << " error" << std::endl;
        return false;
      }
      std::string line;
      int cnt = 1;
      while(std::getline(in, line))
      {
        DocInfo *doc = BuildForwardList(line);//返回最新构建的数据
        if (nullptr == doc) {
          std::cerr << "create " << line << " error" << std::endl;
          continue;
        }
        cnt++;
        if(cnt%50==0)
        {
          std::cout<<"当前建立的索引文档"<<cnt<<std::endl;
        }
        BuildInvertedList(*doc);
      }
      return true;
  }
  public:
  DocInfo *BuildForwardList(const std::string &line)
   {
    std::vector<std::string> results;
    const std::string sep = "\3";

    ns_util::StringUtil::Split(line, &results, sep);//反序列化
    
    if (results.size() != 3)
    {
      return nullptr;
    }
    DocInfo doc;
    doc.title = results[0];
    doc.content = results[1];
    doc.url = results[2];
    doc.doc_id = forward_index.size();//位置的对应关系

    forward_index.push_back(std::move(doc)); // move?-避免不必要的深拷贝在添加元素时调用的是移动构造函数
    return &forward_index.back();//移动构造函数只会移动数据（如指针、资源句柄），不会进行深拷贝

  }
  bool BuildInvertedList(const DocInfo &doc)
  {
      struct word_cnt 
      {
        int title_cnt;
        int content_cnt;

        word_cnt():title_cnt(0),content_cnt(0){}//作用？
      };
      std::unordered_map<std::string, word_cnt> word_map;
      
      std::vector<std::string> title_words;
      ns_util::JiebaUtil::CutForString(doc.title, &title_words);//分解中文语句-title
      for (std::string title : title_words) 
      {
        boost::to_lower(title);//避免大小写歧义
        word_map[title].title_cnt++;
      }
      std::vector<std::string> content_words;
      ns_util::JiebaUtil::CutForString(doc.content, &content_words);//分解中文语句-content
      for (std::string content : content_words) 
      {
        boost::to_lower(content);
        word_map[content].content_cnt++;
      }

#define X 10
#define Y 1
    for (auto &word_pair : word_map)
    {
      InvertedElem item;
      item.doc_id = doc.doc_id;
      item.word = word_pair.first;
      item.weight =X * word_pair.second.title_cnt + Y * word_pair.second.content_cnt;
      InvertedList &inverted_list = inverted_index[word_pair.first];
      inverted_list.push_back(std::move(item));
    }

    return true;
  }
};
Index* Index::instance=nullptr;//这不是重新声明？静态成员变量
//类声明中只是声明了静态成员变量，类外的定义才是正式分配内存并进行初始化的地方，
//编译器需要完整的类型信息来正确地进行这些操作。
} // namespace ns_index
/*
二进制模式打开文件有以下几个特殊用途和重要意义
防止数据格式转换（换行符或 EOF），处理非文本文件，字节精确读写，跨平台一致性

正排索引-文档ID-数据
Split分解数据

倒排索引-字词-属性（ID,优先级，字词）
Jiebacpp--基于词库-对中文语句解剖
计算字词在title/content出现次数

为什么创建单例模式
唯一实例：类只能有一个实例，并且这个实例在整个应用程序中共享。
全局访问点：提供一个全局的静态方法来获取该唯一实例，方便在任何地方访问。
延迟初始化：实例通常是在第一次需要时创建，避免不必要的资源浪费
*/